/*
 * Copyright (c) 2024 iSOFT INFRASTRUCTURE SOFTWARE CO., LTD.
 * easyAda is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#include <core/addrspace.h>
#include <core/allocate.h>
#include <core/schedule.h>
#include <core/task.h>
#include <stdlib/assert.h>
#include <stdlib/stdint.h>
#include <tools/macros.h>
#include <caps/mutex.h>
#include <caps/cond.h>
#include <caps/addrspace.h>
#include <caps/cnode.h>
#include <caps/endpoint.h>
#include <caps/notification.h>
#include <caps/ram.h>
#include <caps/task.h>
#include <uapi/caps/ram.h>
#include <uapi/errors.h>
#include <uapi/ipcbuffer.h>
#include <uapi/ipcops.h>

static inline void cap_ram_set_info(capability_t *cap, unsigned long info)
{
    cap_set_long_info(cap, info);
}

static inline unsigned long cap_ram_mk_info(unsigned long desc, unsigned int direct)
{
    assert((desc & 0x1) == 0);

    if (direct != 0U) {
        desc |= bitmask(CAP_RAM_ATTR_BIT_D);
    }
    return desc;
}

static inline void cap_ram_set_type(capability_t *cap, unsigned int type)
{
    cap_set_short_info(cap, type);
}

void cap_ram_init(capability_t *cap, unsigned long desc, unsigned int type, unsigned int direct)
{
    cap_basic_init(cap, CAP_RAM);
    cap_ram_set_info(cap, cap_ram_mk_info(desc, direct));
    cap_ram_set_type(cap, type);
}

static inline int ram_check_range(capability_t *cap, unsigned long paddr, unsigned long size)
{
    unsigned int pb = cap_ram_get_pbase(cap);
    unsigned int s  = cap_ram_get_size(cap);

    return (paddr >= pb) && (size <= s) && (paddr + size <= pb + s);
}

static int handler_mkcap(unsigned long addr, unsigned int type, unsigned long targetslot)
{
    capability_t       *slot    = NULL;
    const unsigned int *objsize = cap_get_capobj_size();
    int                 ret     = -EINVALID;
    unsigned long      *args    = NULL;

    if ((type >= CAP_COUNT) || (objsize == NULL)) {
        return ret;
    }

    slot = cnode_cptr_lookup(task_get_cnode(current()), targetslot, sizeof(unsigned long) * 8);
    if (slot == NULL) {
        return ret;
    }

    if (!cap_is_empty(slot)) {
        ret = -EOVERLAP;
        return ret;
    }

    switch (type) {
        case CAP_THREAD:
            task_set_cptr((tcb_t *)addr, targetslot);
            task_init((tcb_t *)addr, TTYPE_USER);
            cap_task_init(slot, (tcb_t *)addr);
            ret = ESUCCESS;
            break;

        case CAP_EP:
            endpoint_init((endpoint_t *)addr);
            cap_ep_init(slot, (endpoint_t *)addr, 0);
            ret = ESUCCESS;
            break;

        case CAP_NTFN:
            notification_init((notification_t *)addr);
            cap_ntfn_init(slot, (notification_t *)addr);
            ret = ESUCCESS;
            break;

        case CAP_CNODE:
            break;

        case CAP_ADDRSPACE:
            break;

        case CAP_MUTEX:
            args = ipcbuffer_get_msgs(task_get_ipcbuffer(current()));
            mutex_init((mutex_t *)addr, (int)(args[3]));
            cap_mutex_init(slot, (mutex_t *)addr);
            ret = ESUCCESS;
            break;

	case CAP_COND:
	    cond_init((cond_t *)addr);
	    cap_cond_init(slot, (cond_t *)addr);
	    ret = ESUCCESS;
	    break;

        default:
            break;
    }
    return ret;
}

void cap_ram_dispatcher(capability_t *cap, unsigned long tag, unsigned long m0, unsigned long m1, unsigned long m2,
                        unsigned long m3, unsigned long m4, unsigned long m5)
{
    unsigned int  type = cap_ram_get_type(cap);
    unsigned long cptr = ipcbuffer_get_msgs(task_get_ipcbuffer(current()))[2];

    task_set_retcode(current(), ERR_TO_RETCODE(-ENOSYS));

    switch (msgtag_get_op(tag)) {
        case CAP_RAM_MKCAP:
            if ((type == CAP_RAM_KERNEL) && (msgtag_get_len(tag) == 3)) {
                task_set_retcode(current(), handler_mkcap(m0, m1, cptr));
            } else {
                task_set_retcode(current(), ERR_TO_RETCODE(-EPERMIT));
            }
            break;

        case CAP_RAM_FREE:
            if (type == CAP_RAM_KERNEL) {
                task_set_retcode(current(), ESUCCESS);
            }
            break;

        case CAP_RAM_MAP:
            if ((type != CAP_RAM_KERNEL) && (msgtag_get_len(tag) == 5) && ram_check_range(cap, m0, m1)) {
                capability_t *addrcap;
                addrspace_t  *addrspace = NULL;

                if (cptr == 0) {
                    addrspace = task_get_addrspace(current());
                } else {
                    addrcap = cnode_cptr_lookup(task_get_cnode(current()), cptr, sizeof(int));
                    if (addrcap != NULL) {
                        addrspace = cap_addrspace_get_obj(addrcap);
                    }
                }

                if (addrspace != NULL) {
                    unsigned long vbase  = ipcbuffer_get_msgs(task_get_ipcbuffer(current()))[3];
                    unsigned long attr   = ipcbuffer_get_msgs(task_get_ipcbuffer(current()))[4];
                    unsigned long result = addrspace_map(addrspace, m0, vbase, m1, attr);
                    task_set_retcode(current(), result);
                }
            }
            break;

        default:
            break;
    }
}
